﻿using System;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media.Imaging;
using System.Windows.Threading;
using Microsoft.Phone.Tasks;
using Service4u2.Common;
using MoAds.AdAdapters;
using System.Collections.Generic;

namespace MoAds
{
    /// <summary>
    /// A custom control for displaying Ads.
    /// </summary>
    [TemplatePart(Name = "PART_AdRoot", Type = typeof(Panel))]
    [TemplatePart(Name = "PART_AdImage", Type = typeof(Image))]
    [TemplatePart(Name = "PART_AdText", Type = typeof(TextBlock))]
    [TemplateVisualState(Name = "Normal", GroupName = "VisualStates")]
    [TemplateVisualState(Name = "Loading", GroupName = "VisualStates")]
    [TemplateVisualState(Name = "Error", GroupName = "VisualStates")]
    public class AdDisplayControl : Control, IDisposable
    {
        #region Private members
        
        protected Panel adRoot;
        protected Image adImage;
        protected TextBlock adText;
        protected Button adButton;

        protected DispatcherTimer adReloadTimer = new DispatcherTimer();

        protected EventHandler<ManipulationStartedEventArgs> AdTouchHandler;

        protected EventHandler<EventArgs<Exception>> AdExceptionHandler;

        private Queue<AdResponse> adResponseQueue = new Queue<AdResponse>();

        protected List<AdAdapter> SortedAdAdapterList;

        private bool forceDisplay;

        #endregion

        #region Dependency Properties

        /// <summary>
        /// Gets or sets the ad adapter.
        /// </summary>
        /// <value>The ad adapter.</value>
        public AdAdapterCollection AdAdapters
        {
            get { return (AdAdapterCollection)GetValue(AdAdaptersProperty); }
            set { SetValue(AdAdaptersProperty, value); }
        }

        // Using a DependencyProperty as the backing store for AdAdapter.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty AdAdaptersProperty =
            DependencyProperty.Register("AdAdapters", 
                typeof(AdAdapterCollection), 
                typeof(AdDisplayControl), 
                new PropertyMetadata(null, new PropertyChangedCallback(OnAdAdaptersChanged)));

        static void OnAdAdaptersChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args)
        {
            if (!(obj is AdDisplayControl))
                return;

            var disp = obj as AdDisplayControl;
            var adapters = args.NewValue as AdAdapterCollection;

            if (args.OldValue is AdAdapter)
            {
                // Remove the old event handlers.
                disp.RemoveAdAdapterHandlers(args.OldValue as AdAdapterCollection);
            }

            if (adapters != null)
            {
                foreach (AdAdapter adapter in adapters)
                {
                    // Add event handlers.
                    adapter.AdResponseEvent += disp.AdAdapterResponseHandler;
                    adapter.ExceptionEvent += disp.AdExceptionHandler;
                }

                disp.SortedAdAdapterList = new List<AdAdapter>();

                disp.SortedAdAdapterList.AddRange(adapters);

                disp.SortedAdAdapterList.Sort(delegate(AdAdapter p1, AdAdapter p2)
                { return p1.Priority.CompareTo(p2.Priority); });
            }
        }

        /// <summary>
        /// Gets or sets the refresh seconds.
        /// </summary>
        /// <value>The refresh seconds.</value>
        public double RefreshSeconds
        {
            get { return (double)GetValue(RefreshSecondsProperty); }
            set { SetValue(RefreshSecondsProperty, value); }
        }

        // Using a DependencyProperty as the backing store for RefreshSeconds.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty RefreshSecondsProperty =
            DependencyProperty.Register("RefreshSeconds", typeof(double), typeof(AdDisplayControl), new PropertyMetadata(60.00, new PropertyChangedCallback(OnRefreshSecondsChanged)));

        static void OnRefreshSecondsChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args)
        {
            double newValue = 60;
            if (!VerifyDepObjAndGetNewValue(obj, args, out newValue))
                return;

            // Update the refresh timer.
            ((AdDisplayControl)obj).UpdateRefreshTimer(newValue);
        }

        public string LoadedAdImageUri
        {
            get { return (string)GetValue(LoadedAdImageUriProperty); }
            set { SetValue(LoadedAdImageUriProperty, value); }
        }

        // Using a DependencyProperty as the backing store for LoadedAdImageUri.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty LoadedAdImageUriProperty =
            DependencyProperty.Register("LoadedAdImageUri", typeof(string), typeof(AdDisplayControl), new PropertyMetadata(string.Empty));

        public string LoadedAdText
        {
            get { return (string)GetValue(LoadedAdTextProperty); }
            set { SetValue(LoadedAdTextProperty, value); }
        }

        // Using a DependencyProperty as the backing store for LoadedAdText.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty LoadedAdTextProperty =
            DependencyProperty.Register("LoadedAdText", typeof(string), typeof(AdDisplayControl), new PropertyMetadata(string.Empty));

        public string LoadedAdUri
        {
            get { return (string)GetValue(LoadedAdUriProperty); }
            set { SetValue(LoadedAdUriProperty, value); }
        }

        // Using a DependencyProperty as the backing store for LoadedAdUri.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty LoadedAdUriProperty =
            DependencyProperty.Register("LoadedAdUri", typeof(string), typeof(AdDisplayControl), new PropertyMetadata(string.Empty));

        public Boolean DebugMode
        {
            get { return (Boolean)GetValue(DebugModeProperty); }
            set { SetValue(DebugModeProperty, value); }
        }

        public static readonly DependencyProperty DebugModeProperty =
            DependencyProperty.Register("DebugMode", typeof(Boolean), typeof(AdDisplayControl), new PropertyMetadata(false));

        #endregion

        #region Constructor and OnApplyTemplate (Base methods)

        /// <summary>
        /// Initializes a new instance of the <see cref="AdDisplayControl"/> class.
        /// </summary>
        public AdDisplayControl()
            : base()
        {
            DefaultStyleKey = typeof(AdDisplayControl);

            // Create our ad touch handler.
            AdTouchHandler = (s, e) => HandleAdTouch();

            // Create our error handler.
            AdExceptionHandler = (object s, EventArgs<Exception> e) =>
            {
                if (DebugMode)
                {
                    if (e.Argument is WebException)
                        LoadedAdText = "Connection Error";
                    else
                        LoadedAdText = "Error Getting Ads";

                    VisualStateManager.GoToState(this, "Error", true);
                }
                else
                {
                    // Show the existing ad if present.
                    if (!string.IsNullOrEmpty(this.LoadedAdUri))
                        VisualStateManager.GoToState(this, "Normal", true);
                }
            };
        }

        /// <summary>
        /// When overridden in a derived class, is invoked whenever application code or internal processes (such as a rebuilding layout pass) call <see cref="M:System.Windows.Controls.Control.ApplyTemplate"/>. In simplest terms, this means the method is called just before a UI element displays in an application. For more information, see Remarks.
        /// </summary>
        public override void OnApplyTemplate()
        {
            base.OnApplyTemplate();

            adRoot = GetTemplateChild("PART_AdRoot") as Panel;
            if (adRoot == null)
                throw new InvalidOperationException("Failed to find PART_AdRoot Panel in template.");

            adImage = GetTemplateChild("PART_AdImage") as Image;
            if (adImage == null)
                throw new InvalidOperationException("Failed to find PART_AdImage");

            adText = GetTemplateChild("PART_AdText") as TextBlock;
            if (adText == null)
                throw new InvalidOperationException("Failed to find PART_AdText");

            // Remove any existing handlers.
            adRoot.ManipulationStarted -= AdTouchHandler;
            
            // Hook up our touch events to the add parts.
            adRoot.IsHitTestVisible = true;
            adRoot.ManipulationStarted += AdTouchHandler;
            
            // This part is not required, but used in the default template.
            adButton = GetTemplateChild("PART_AdGo") as Button;
            if (adButton != null)
            {
                adButton.Click -= AdButtonClick;
                adButton.Click += AdButtonClick;
            }

            // Create the default ad reload timer, start loading in 1 second, then it resets to the assigned interval.
            adReloadTimer.Interval = TimeSpan.FromSeconds(1);
            adReloadTimer.Tick -= new EventHandler(adReloadTimer_Tick);
            adReloadTimer.Tick += new EventHandler(adReloadTimer_Tick);
            adReloadTimer.Start();
        }

        #endregion

        #region Public methods

        

        /// <summary>
        /// Refreshes the ad now.
        /// </summary>
        public void RefreshAdNow()
        {
            adReloadTimer.Stop();

            DisplayNextAd();

            adReloadTimer.Start();
        }

        #endregion

        #region Other Methods

        /// <summary>
        /// Handles the ad button click.
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The <see cref="System.Windows.RoutedEventArgs"/> instance containing the event data.</param>
        void AdButtonClick(object sender, RoutedEventArgs e)
        {
            HandleAdTouch();
        }

        /// <summary>
        /// Handles the ad touch.
        /// </summary>
        private void HandleAdTouch()
        {
            if (String.IsNullOrEmpty(this.LoadedAdUri))
                return;

            // start up a web browser task to show the ad
            WebBrowserTask task = new WebBrowserTask();
            task.URL = this.LoadedAdUri;
            task.Show();
        }

        /// <summary>
        /// Handles the Tick event of the adReloadTimer control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param>
        void adReloadTimer_Tick(object sender, EventArgs e)
        {
            adReloadTimer.Interval = TimeSpan.FromSeconds(this.RefreshSeconds);
            adReloadTimer.Stop();
            DisplayNextAd();
            adReloadTimer.Start();
        }

        /// <summary>
        /// Begins the loading of an ad.
        /// </summary>
        private void DisplayNextAd()
        {
            if (adResponseQueue.Count < 2) LoadAds();

            if (adResponseQueue.Count == 0)
            {
                forceDisplay = true;
            }
            else
            {
                VisualStateManager.GoToState(this, "Loading", true);

                var adResponse = adResponseQueue.Dequeue();

                if (string.IsNullOrEmpty(adResponse.AdText) || string.IsNullOrEmpty(adResponse.ClickUrl))
                {
                    // Show the existing ad if present.
                    if (!string.IsNullOrEmpty(this.LoadedAdUri))
                        VisualStateManager.GoToState(this, "Normal", true);

                    return;
                }

                this.LoadedAdImageUri = adResponse.ImageUrl;
                adImage.Source = adResponse.ImageSource;
                this.LoadedAdText = adResponse.AdText;
                this.LoadedAdUri = adResponse.ClickUrl;

                VisualStateManager.GoToState(this, "Normal", true);
            }
        }

        /// <summary>
        /// Loads the ad info async.
        /// </summary>
        private void LoadAds()
        {

            if (AdAdapters == null || AdAdapters.Count == 0)
            {   
                this.LoadedAdText = "No Ad Adapter Provided";
                return;
            }

            foreach (AdAdapter adAdapter in SortedAdAdapterList)
            {
                Int32 count = adAdapter.AdCount;
                if (count < 1) count = 1;

                for (int i = 0; i < count; i++)
                {
                    adAdapter.BeginAdRequestAsync();
                }
            }
        }

        /// <summary>
        /// Handles the ad adapter response.
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="args">The <see cref="Service4u2.Common.EventArgs&lt;MoAds.Services.AdResponse&gt;"/> instance containing the event data.</param>
        private void AdAdapterResponseHandler(object sender, EventArgs<AdResponse> args)
        {
            adResponseQueue.Enqueue(args.Argument);

            if (forceDisplay)
            {
                forceDisplay = false;
                DisplayNextAd();
            }
        }

        /// <summary>
        /// Updates the refresh timer.
        /// </summary>
        /// <param name="newRefreshSeconds">The new refresh seconds.</param>
        internal void UpdateRefreshTimer(double newRefreshSeconds)
        {
            if (adReloadTimer == null)
                adReloadTimer = new DispatcherTimer();

            adReloadTimer.Stop();
            adReloadTimer.Interval = TimeSpan.FromSeconds(newRefreshSeconds);
            adReloadTimer.Start();
        }

        /// <summary>
        /// Removes the ad adapter handlers.
        /// </summary>
        /// <param name="oldAdapt">The old adapter.</param>
        internal void RemoveAdAdapterHandlers(AdAdapterCollection adapterCollection)
        {
            foreach (AdAdapter adapter in adapterCollection)
            {
                adapter.AdResponseEvent -= AdAdapterResponseHandler;
                adapter.ExceptionEvent -= AdExceptionHandler;
            }
        }

        /// <summary>
        /// Verifies the dependency object is an AdDisplay control and gets new value from args.
        /// </summary>
        /// <typeparam name="TValue">The type of the value.</typeparam>
        /// <param name="obj">The obj.</param>
        /// <param name="args">The <see cref="System.Windows.DependencyPropertyChangedEventArgs"/> instance containing the event data.</param>
        /// <param name="value">The value.</param>
        /// <returns></returns>
        static bool VerifyDepObjAndGetNewValue<TValue>(DependencyObject obj, DependencyPropertyChangedEventArgs args, out TValue value)
        {
            value = default(TValue);
            if (!(obj is AdDisplayControl))
                return false;

            value = (TValue)args.NewValue;
            return true;
        }

        #endregion

        public void StopRefreshing()
        {
            adReloadTimer.Stop();
        }

        public void Dispose()
        {
            this.StopRefreshing();
        }
    }

}
